---
patternId: files/open-a-directory
---

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="icon"
      href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
    />
    <title>How to open a directory</title>
    <style>
      :root {
        color-scheme: dark light;
      }

      html {
        box-sizing: border-box;
      }

      *,
      *:before,
      *:after {
        box-sizing: inherit;
      }

      body {
        margin: 1rem;
        font-family: system-ui, sans-serif;
      }
    </style>
  </head>
  <body>
    <h1>How to open a directory</h1>
    <button type="button">Open directory</button>
    <pre></pre>

    {% set script %}
      const button = document.querySelector('button');
      const pre = document.querySelector('pre');

      const openDirectory = async (mode = "read") => {
        // Feature detection. The API needs to be supported
        // and the app not run in an iframe.
        const supportsFileSystemAccess =
          "showDirectoryPicker" in window &&
          (() => {
            try {
              return window.self === window.top;
            } catch {
              return false;
            }
          })();
        // If the File System Access API is supported…
        if (supportsFileSystemAccess) {
          let directoryStructure = undefined;

          const getFiles = async (dirHandle, path = dirHandle.name) => {
            const dirs = [];
            const files = [];
            for await (const entry of dirHandle.values()) {
              const nestedPath = `${path}/${entry.name}`;
              if (entry.kind === "file") {
                files.push(
                  entry.getFile().then((file) => {
                    file.directoryHandle = dirHandle;
                    file.handle = entry;
                    return Object.defineProperty(file, "webkitRelativePath", {
                      configurable: true,
                      enumerable: true,
                      get: () => nestedPath,
                    });
                  })
                );
              } else if (entry.kind === "directory") {
                dirs.push(getFiles(entry, nestedPath));
              }
            }
            return [
              ...(await Promise.all(dirs)).flat(),
              ...(await Promise.all(files)),
            ];
          };

          try {
            const handle = await showDirectoryPicker({
              mode,
            });
            directoryStructure = getFiles(handle, undefined);
          } catch (err) {
            if (err.name !== "AbortError") {
              console.error(err.name, err.message);
            }
          }
          return directoryStructure;
        }
        // Fallback if the File System Access API is not supported.
        return new Promise((resolve) => {
          const input = document.createElement('input');
          input.type = 'file';
          input.webkitdirectory = true;

          input.addEventListener('change', () => {
            let files = Array.from(input.files);
            resolve(files);
          });
          if ('showPicker' in HTMLInputElement.prototype) {
            input.showPicker();
          } else {
            input.click();
          }
        });
      };

      button.addEventListener('click', async () => {
        const filesInDirectory = await openDirectory();
        if (!filesInDirectory) {
          return;
        }
        Array.from(filesInDirectory).forEach((file) => (pre.textContent += `${file.name}\n`));
      });
    {% endset %}
    <script>{{ script | minifyJs | cspHash | safe }}</script>
  </body>
</html>
